package cn.janescott.framework.dubbo.listener;

import cn.janescott.framework.dubbo.DubboConsumerAutoConfiguration;
import cn.janescott.framework.dubbo.DubboProperties;
import cn.janescott.framework.dubbo.bean.ClassIdBean;
import cn.janescott.framework.dubbo.utils.FieldCollectUtils;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * dubbo consumer reference check while spring boot application started
 *
 * @author scott
 * @date 2019/02/12
 **/
public class ConsumerReferencePreparedListener implements ApplicationListener<ApplicationStartedEvent>, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        DubboProperties dubboProperties = applicationContext.getBean(DubboProperties.class);
        if (dubboProperties != null && dubboProperties.isCheck()) {
            checkAndResetDubboReference();
        }
    }

    /**
     * 检测&&手动注入dubbo代理对象
     */
    private void checkAndResetDubboReference() {
        //获取consumerAutoConfiguration Bean
        DubboConsumerAutoConfiguration consumerAutoConfiguration = applicationContext.getBean(DubboConsumerAutoConfiguration.class);
        //获取所有加载的bean
        String[] beans = applicationContext.getBeanDefinitionNames();
        try {
            for (String beanName : beans) {
                Class<?> beanType = applicationContext.getType(beanName);
                Object bean = applicationContext.getBean(beanName);
                // 排除掉DubboConsumerAutoConfiguration
                if (bean.equals(consumerAutoConfiguration)) {
                    continue;
                }

                List<Field> fieldList = new ArrayList<>();
                /* 1、收集所有当前类的Reference属性（包括私有属性） */
                FieldCollectUtils.handleCurrentClass(beanType, fieldList);
                /* 2、递归收集所有父类的Reference属性（包括私有属性） */
                FieldCollectUtils.handleSuperClass(beanType, fieldList);
                /* 3、为Reference注解的属性设置代理对象 */
                for (Field field : fieldList) {
                    Reference reference = field.getAnnotation(Reference.class);
                    field.setAccessible(true);
                    Object obj = field.get(bean);
                    //如果dubbo reference已经初始化，则直接返回
                    if (null != obj) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("check consumer bean: " + beanName + " -> " + field.getName() + " finished!");
                        }
                        continue;
                    }
                    ReferenceBean<?> referenceBean =
                            consumerAutoConfiguration.getConsumerBean(beanName, field, reference);
                    Class<?> interfaceClass = referenceBean.getInterfaceClass();
                    String group = referenceBean.getGroup();
                    String version = referenceBean.getVersion();
                    ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
                    Object dubboReference =
                            DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
                    if (dubboReference == null) {
                        synchronized (this) {
                            // double check
                            dubboReference =
                                    DubboConsumerAutoConfiguration.getDubboReference(classIdBean);
                            if (dubboReference == null) {
                                referenceBean.afterPropertiesSet();
                                // dubboReference should not be null, otherwise it will cause
                                // NullPointerException
                                dubboReference = referenceBean.getObject();
                                // put reference bean into Map
                                DubboConsumerAutoConfiguration.setDubboReference(classIdBean,
                                        dubboReference);
                            }
                        }
                    }
                    field.setAccessible(true);
                    field.set(bean, dubboReference);
                    if (logger.isDebugEnabled()) {
                        logger.debug("check consumer bean: " + beanName + " -> " + field.getName() + " reset!");
                    }
                }
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw new RuntimeException("load dubbo reference field error!", ex);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
